package com.wordpress.hybrid.util;

import android.os.SystemClock;
import android.util.Log;

import org.apache.http.client.ClientProtocolException;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/** Support Multiple Processes */
public class Downloads {
    static final String TAG = "Downloads";

    public static final int STATE_UNINIT = 0;
    public static final int STATE_START = 1;
    public static final int STATE_IN_PROCESS = 2;
    public static final int STATE_COMPLETED = 3;
    public static final int STATE_FAILED = 4;
    public static final int STATE_CANCELED = 5;

    public interface OnDownloadListener {
        /**
         * 下载开始后，每次写新的数据时调用
         * @param dst
         * @param url
         * @param progress
         * @param total
         * @return
         * @NOTE
         * 这里的progress是已经读到本地的数据，但是并不保证和dst的size完全一致
         * （因为写文件使用的是BufferedOutputStream，不确定什么时候真正被写到文件）
         */
        public boolean onProgressUpdate(File dst, String url, long progress, long total);
        public void onStateChanged(File dst, String url, int state, long total);
    }

    public static boolean ensureParentExists(File file) {
        File parent = file.getParentFile();
        if (parent.exists()) {
            if (parent.isDirectory()) {
                return true;
            }
            parent.delete();
        }
        // we will not attempt to create the first directory in the path
        // (for example, do not create /sdcard if the SD card is not mounted)
        int secondSlash = file.getAbsolutePath().indexOf('/', 1);
        if (secondSlash < 1) return false;
        String directoryPath = file.getAbsolutePath().substring(0, secondSlash);
        File directory = new File(directoryPath);
        if (!directory.exists())
            return false;
        return parent.mkdirs();
    }

    public static boolean download(File dst, String url, OnDownloadListener l) {
        if (dst.exists()) {
            Log.i(TAG, "file already exists, path" + dst.getAbsolutePath());
            dst.delete();
        }

        if (!ensureParentExists(dst)) {
            Log.i(TAG, "create dir failed, path" + dst.getAbsolutePath());
            return false;
        }

        BufferedInputStream bis = null;
        OutputStream bos = null;
        int state = STATE_UNINIT;
        final long[] total = new long[1];
        long progress = 0;
        try {
            Log.i(TAG, "Start connect, path=" + dst);
            long c = SystemClock.uptimeMillis();
            bis = new BufferedInputStream(new FileInputStream(url));
            // TODO: 暂时使用本地资源包测试，改为网络地址后，需调用注释的这行
//            bis = new BufferedInputStream(NetworkUtil.doHttpGet(url, 20000, 20000 , total));
            Log.d(TAG, "Connect time path=" + dst + ", time=" + (SystemClock.uptimeMillis() - c) + ", total=" + total[0]);
            bos = new FileOutputStream(dst);
            if (l == null) { // 如果需要监听下载进度，就不能用BufferedOutputStream
                bos = new BufferedOutputStream(bos);
            }

            if (total[0] > 0) {
                ensureFileSize(dst, total[0]);
            }

            state = STATE_START;
            if (l != null) {
                l.onStateChanged(dst, url, STATE_START, total[0]);
            }

            final byte[] buf = new byte[1024];
            int i = 0;
            state = STATE_IN_PROCESS;
            while ((i = bis.read(buf, 0, buf.length)) != -1) {
                bos.write(buf, 0, i);
                progress += i;
                if (l != null) {
                    if (!l.onProgressUpdate(dst, url, progress, total[0])) {
                        state = STATE_CANCELED;
                        break;
                    }
                }
            }

            bos.flush();
        } catch (ClientProtocolException e) {
            Log.e(TAG, "File=" + dst + ", url=" + url, e);
//        } catch (URISyntaxException e) {
//            Log.e(TAG, "File=" + dst + ", url=" + url, e);
        } catch (IOException e) {
            Log.e(TAG, "File=" + dst + ", url=" + url, e);
        } finally {
            StreamHelper.closeSafe(bis);
            StreamHelper.closeSafe(bos);

            // TODO: 暂时使用本地资源包测试，改为网络地址后，需判断大小
            if (state == STATE_IN_PROCESS /*&& total[0] == progress*/) {
                state = STATE_COMPLETED;
            }

            if (state != STATE_CANCELED && state != STATE_COMPLETED) {
                state = STATE_FAILED;
            }

            if (state != STATE_COMPLETED) {
                dst.delete();
            }

            Log.i(TAG, "File=" + dst + ", url=" + url + ", state=" + state);

            if (l != null) {
                l.onStateChanged(dst, url, state, total[0]);
            }
        }

        return state == STATE_COMPLETED;
    }

    private static boolean ensureFileSize(File f, long size) {
        if (f.exists()) {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile(f, "rw");
                raf.setLength(size);
                Log.i(TAG, "Ensure file size success: path=" + f + ", size=" + size);
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                StreamHelper.closeSafe(raf);
            }
        }

        Log.i(TAG, "Ensure file size failed: path=" + f + ", size=" + size);
        return false;
    }

    public static boolean extractAndRecycle(File src, File dstDir) {
        if (src == null || !src.exists()) {
            return false;
        }

        InputStream is = null;
        OutputStream os = null;
        ZipFile zip = null;
        try {
            zip = new ZipFile(src);
            Enumeration<? extends ZipEntry> it = zip.entries();
            while (it.hasMoreElements()) {
                ZipEntry e = it.nextElement();
                if (e.isDirectory()) {
                    continue;
                }
                is = zip.getInputStream(e);
                File f = FileOperations.getAbsoluteFile(dstDir, e.getName());
                FileOperations.ensureParentExists(f);
                os = new FileOutputStream(f);
                FileOperations.copyFile(is, os);
                is.close();
                os.close();
            }
            src.delete();
            return true;
        } catch (ZipException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            StreamHelper.closeSafe(is);
            StreamHelper.closeSafe(os);
            StreamHelper.closeSafe(zip);
        }
        return false;
    }

}
